﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace MyRaceGame
{
    public class RaceTrack
    {
        RaceTrack(List<Vector2> positions, int nDivisions, float trackWidth, GraphicsDevice graphicsDevice)
        {
            this.nDivisions = nDivisions;
            this.trackWidth = trackWidth;
            this.graphicsDevice = graphicsDevice;

            this.positions = InterpolatePositions(positions, nDivisions);
            CreateBuffers(trackWidth);

            effect = new BasicEffect(graphicsDevice);
        }

        public RaceTrack(List<Vector2> positions, int nDivisions, float trackWidth, GraphicsDevice graphicsDevice, 
            ContentManager content, string raceTileAssetName) :
            this(positions, nDivisions, trackWidth, graphicsDevice)
        {
            this.texture = content.Load<Texture2D>(raceTileAssetName);
        }

        public RaceTrack(List<Vector2> positions, int nDivisions, float trackWidth, GraphicsDevice graphicsDevice,
            Texture2D raceTileAsset) :
            this(positions, nDivisions, trackWidth, graphicsDevice)
        {
            this.texture = raceTileAsset;
        }

        Vector2 CatmullRomV2(Vector2 v1, Vector2 v2, Vector2 v3, Vector2 v4, float amount)
        {
            return new Vector2(MathHelper.CatmullRom(v1.X, v2.X, v3.X, v4.X, amount),
                MathHelper.CatmullRom(v1.Y, v2.Y, v3.Y, v4.Y, amount));
        }

        List<Vector2> InterpolatePositions(List<Vector2> positions, int nDivisions)
        {
            List<Vector2> newPositions = new List<Vector2>();

            positions.Insert(0, positions[positions.Count - 1]);
            positions.Add(positions[1]);

            for (int i = 1; i < positions.Count - 2; i++)
            {
                newPositions.Add(positions[i]);

                for (int j = 0; j < nDivisions; j++)
                {
                    float amt = (float)(j + 1) / (float)(nDivisions + 2);
                    Vector2 interp = CatmullRomV2(positions[i - 1], positions[i], 
                        positions[i + 1], positions[i + 2], amt);
                    newPositions.Add(interp);
                }
            }

            newPositions.Add(positions[positions.Count - 1]);

            return newPositions;
        }

        void CreateBuffers(float trackWidth)
        {
            nVertices = positions.Count * 2;

            VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[nVertices];

            int j = 0;

            //Vector2 lastNormal = positions[positions.Count - 1] - positions[positions.Count - 2];
            //positions.Add(positions[positions.Count - 1] + lastNormal);

            float uvY = 0;

            for (int i = 0; i < positions.Count - 1; i++)
            {
                Vector3 position = new Vector3(positions[i].X, 0, positions[i].Y);
                Vector3 nextPosition = new Vector3(positions[i + 1].X, 0, positions[i + 1].Y);

                Vector3 forward = nextPosition - position;
                float length = forward.Length();
                forward.Normalize();
                Vector3 side = -Vector3.Cross(forward, Vector3.Up) * trackWidth;

                vertices[j++] = new VertexPositionNormalTexture(position - side, Vector3.Up, new Vector2(0, uvY));
                vertices[j++] = new VertexPositionNormalTexture(position + side, Vector3.Up, new Vector2(1, uvY));

                uvY += length;
            }

            vertices[vertices.Length - 3].Position = vertices[1].Position;
            vertices[vertices.Length - 4].Position = vertices[0].Position;

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].TextureCoordinate = new Vector2(vertices[i].TextureCoordinate.X, vertices[i].TextureCoordinate.Y / uvY * 35);

            trackLength = uvY;

            positions.RemoveAt(positions.Count - 1);

            nIndices = (positions.Count - 1) * 6;

            int[] indices = new int[nIndices];

            j = 0;

            for (int i = 0; i < positions.Count - 1; i++)
            {
                int i0 = i * 2;

                indices[j++] = i0;
                indices[j++] = i0 + 1;
                indices[j++] = i0 + 2;
                indices[j++] = i0 + 2;
                indices[j++] = i0 + 1;
                indices[j++] = i0 + 3;
            }

            vb = new VertexBuffer(graphicsDevice, typeof(VertexPositionNormalTexture), vertices.Length, BufferUsage.WriteOnly);
            vb.SetData<VertexPositionNormalTexture>(vertices);

            short[] indShorts = new short[indices.Length];
            for (int i = 0; i < indices.Length; i++)
                indShorts[i] = (short)indices[i];

            ib = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, indices.Length, BufferUsage.WriteOnly);
            ib.SetData<short>(indShorts);
        }

        public Vector2 TracePath(float distance, out Vector2 direction)
        {
            while (distance > trackLength)
                distance -= trackLength;

            int i = 0;

            while (true)
            {
                int next = i + 1 == positions.Count ? 0 : i + 1;

                direction = positions[next] - positions[i];
                float length = direction.Length();

                if (length < distance)
                {
                    distance -= length;
                    i++;
                    if (i == positions.Count)
                        i = 0;
                    continue;
                }

                int last = i - 1 == -1 ? positions.Count - 1 : i - 1;
                Vector2 lastDirection = positions[i] - positions[last];
                lastDirection.Normalize();
                direction.Normalize();

                float amt = distance / length;

                direction = Vector2.Lerp(lastDirection, direction, amt);
                return Vector2.Lerp(positions[i], positions[next], amt);
            }
        }

        public void Draw(Matrix View, Matrix Projection)
        {
            effect.World = Matrix.Identity;
            effect.View = View;
            effect.Projection = Projection;
            effect.Texture = texture;
            effect.TextureEnabled = true;
            //effect.EnableDefaultLighting();

            //effect.Parameters["World"].SetValue(Matrix.Identity);
            //effect.Parameters["View"].SetValue(View);
            //effect.Parameters["Projection"].SetValue(Projection);

            graphicsDevice.SetVertexBuffer(vb);
            graphicsDevice.Indices = ib;

            RasterizerState s = new RasterizerState();
            //s.CullMode = CullMode.None;
            //s.FillMode = FillMode.WireFrame;
            graphicsDevice.RasterizerState = s;

            effect.CurrentTechnique.Passes[0].Apply();

            graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices / 3);
        }

        public float TrackLength
        {
            get { return trackLength; }
            set { trackLength = value; }
        }

        List<Vector2> positions;
        VertexBuffer vb;
        IndexBuffer ib;
        int nVertices, nIndices;
        BasicEffect effect;
        float trackLength;

        // Copy for extention
        protected GraphicsDevice graphicsDevice;
        protected int nDivisions;
        protected float trackWidth;
        protected Texture2D texture;
    }
}
